Purpose

The purpose of this notebook is to use the raw travel time data to experiment with different methods of aggregation and score modeling.

Import libraries

library(tidyverse)
library(ggplot2)

# For pretty knitting
library(lemon)
knit_print.data.frame <- lemon_print
knit_print.tbl <- lemon_print
knit_print.summary <- lemon_print

Import Scoring Functions

source('Score_Functions.R')

# normalize_vec(vec, x=0.01, y=0.99, log = FALSE)
# normalize_df(df, x = 0.01, y = 0.99, log = FALSE)

# sum score function : SUM [i..n] (1 / (traveltime_i * std_traveltime_i) + ... ))
# sum_score_fxn(df, weight = FALSE, log_normalize_score = TRUE, normalize_df = FALSE, x=1, y=10)
# function to plot score distributions by type
plot_densities <- function(score_frame1, score_frame2, titl1, titl2) {
  x <- score_frame1 %>%
        ggplot(aes(x = score, color = type)) +
        geom_density() +
        egg::theme_article() +
        theme(aspect.ratio = 0.3) +
        ggtitle(titl1)
  y <- score_frame2 %>%
        ggplot(aes(x = score, color = type)) +
        geom_density() +
        egg::theme_article() +
        theme(aspect.ratio = 0.3)+
        ggtitle(titl2)
  gridExtra::grid.arrange(x, y)
}

Import data


## Import raw Travel Time Matrix (ttm)
ttm <- read.csv('../../data/clean/ttm.csv')

n_origins <- 15197 # known origins
n_amenities <- 346 # known destinations from considered amenities

paste('Origins considered:', round(length(unique(ttm$fromId))/n_origins*100, 2), '%')
[1] "Origins considered: 94.44 %"
paste('Destinations considered:', round(length(unique(ttm$toId))/n_amenities*100, 2), '%')
[1] "Destinations considered: 124.57 %"
paste('Rows = ', nrow(ttm))
[1] "Rows =  5162695"
# convert Ids from double to factor
ttm$fromId <- as.factor(ttm$fromId)
ttm$toId <- as.factor(ttm$toId)

summary(ttm[,3:4])
 avg_unique_time  sd_unique_time   
 Min.   :  0.00   Min.   : 0.1601  
 1st Qu.: 52.54   1st Qu.: 1.9428  
 Median : 72.18   Median : 2.8868  
 Mean   : 72.79   Mean   : 3.4044  
 3rd Qu.: 94.21   3rd Qu.: 4.3813  
 Max.   :119.00   Max.   :35.3553  

Data Wrangling

Wrangling Notes: - Remove skews and extreme values - Due to the diversity in amenity types (which all serve a unique cultural purpose), we’ll consider them independently for accessibility score computations. - Amenities which were interested in studying have already been filtered out in the ttm computation. They are the following: - Museums - Libraries - Galleries - Theatres

Import and join amenity types


target_amenities <- c('gallery', 'museum', 'library or archives', 'theatre/performance and concert hall')
amenities <- read.csv('../../data/clean/vancouver_facilities_2.csv') %>% filter(type %in% target_amenities)

# preview original
sample_n(amenities, 3)

# clean
amenities <- amenities[,c(1,4)] # only need id and type columns
amenities$id <- as.factor(amenities$id)     # convert to factor
amenities$type <- as.factor(amenities$type) # convert to factor

# preview clean
sample_n(amenities, 3)

# view summary
amenities %>% group_by(type) %>% summarise(count = n()) %>% arrange(desc(count))

ttm <- ttm %>% left_join(amenities, by = c('toId' = 'id'))

names(ttm)[names(ttm) == 'avg_unique_time'] <- "avg_time"
names(ttm)[names(ttm) == 'sd_unique_time'] <- "sd_time"

summary(ttm[,3:4])
    avg_time         sd_time       
 Min.   :  0.00   Min.   : 0.1601  
 1st Qu.: 52.54   1st Qu.: 1.9428  
 Median : 72.18   Median : 2.8868  
 Mean   : 72.79   Mean   : 3.4044  
 3rd Qu.: 94.21   3rd Qu.: 4.3813  
 Max.   :119.00   Max.   :35.3553  
sample_n(ttm, 5)

par(mfrow = c(1,2))
plot(density(ttm[,3]), main = 'Travel Time (Density)')
plot(density(ttm[,4]), main = 'Std Dev of Travel Time (Density)')

Replace travel times less than 5 minutes to 5 minutes

This is done to prevent infinity values in the scoring. Normalization will be done to prevent zero values but it still creates a largely skewed score if we include travel times that approach zero. 5 minutes is also a realistic time window for any travel time that may take 0 - 5 minutes.

par(mfrow = c(1, 2))

hist((ttm$avg_time), xlab = 'Original Travel Time', main = '',
     xlim = c(0, 25), ylim = c(0, 120000))

# set travel times <5 minutes to 5 minutes
min_5min <- pmax(ttm$avg_time, 5)
hist(min_5min, xlab = 'Original Travel Time', main = '',
     xlim = c(0, 25), ylim = c(0, 120000))


ttm$avg_time <- min_5min

Correct skew in standard deviation

This will be important to prevent skew amplification in the score computation.

# correct the skew in addition to edges close to zero

temp <- log(ttm$sd_time + 1) # +1 just prevents zero values
plot(density(temp), main = 'Log+1 Standard Deviation Density', xlim = c(0,4))


# set sd_unique_time to be the Log+1 corrected values
ttm$sd_time <- temp

Add Amenity Weights


# Import weight
dest_wts <- read.csv('../../data/amenity_score/poi_index.csv')

# clean
dest_wts <- dest_wts[, c(6,7)] # keep weight, id
names(dest_wts) <- c('weight', 'id')
dest_wts$id <-  as.factor(dest_wts$id)
head(dest_wts)

# see weight distribution
plot(density(dest_wts$weight), main = 'Amenity Popularity Distribution')


# join column
ttm_wts <- left_join(ttm, dest_wts, by = c('toId'='id'))

# If any weights are undefined replace with 1
ttm_wts$weight[is.na(ttm_wts$weight)] <- 1

head(ttm_wts)
NA
NA

Sum Scoring Method


# scores with [1 - 100] df normalization 


na.omit(ttm_wts)->ttm_wts


ttm_scores <- sum_score_fxn(ttm_wts,
                            weight = FALSE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 1, y = 100)

ttm_wtd_scores <- sum_score_fxn(ttm_wts,
                            weight = TRUE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 1, y = 100)

Sum Scoring Method 2 with mean plus sd

ttm_scores_2 <- sum_score_fxn_2(ttm_wts,
                            weight = FALSE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 1, y = 100)

ttm_wtd_scores_2 <- sum_score_fxn_2(ttm_wts,
                            weight = TRUE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 1, y = 100)


# scores with [0.01 - 0.99] df normalization 

ttm_scores2 <- sum_score_fxn(ttm_wts,
                            weight = FALSE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 0.01, y = 0.99)

ttm_wtd_scores2 <- sum_score_fxn(ttm_wts,
                            weight = TRUE,
                            log_normalize_score = TRUE,
                            normalize_df = TRUE, x = 0.01, y = 0.99)

plot_densities(ttm_scores2, ttm_wtd_scores2, 'Unweighted Scores', 'Weighted Scores')

Sum Scoring for the Nearest 1, 2, or 3 Amenities

Note that for nearest 1, the sum is the value itself.


# Keep only the nearest 1, 2, or 3 travel times for each dissemination block

nearest_1_ttm <- ttm_wts %>%
                  group_by(fromId, type) %>%
                  summarise(avg_time = min(avg_time), 
                            sd_time = sd_time[which.min(avg_time)],
                            weight = weight[which.min(avg_time)])

nearest_2_ttm <- ttm_wts %>%
                  group_by(fromId, type) %>%
                  summarise(avg_time = na.omit(sort(avg_time)[1:2]), 
                            sd_time = sd_time[which(na.omit(avg_time == sort(avg_time)[1:2]))], 
                            weight = weight[which(na.omit(avg_time == sort(avg_time)[1:2]))])

nearest_3_ttm <- ttm_wts %>%
                  group_by(fromId, type) %>%
                  summarise(avg_time = na.omit(sort(avg_time)[1:3]), 
                            sd_time = sd_time[which(na.omit(avg_time == sort(avg_time)[1:3]))], 
                            weight = weight[which(na.omit(avg_time == sort(avg_time)[1:3]))])


# scores by nearest amenities

n1_ttm_score <- sum_score_fxn(nearest_1_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n1_wt_ttm_score <- sum_score_fxn(nearest_1_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)

n2_ttm_score <- sum_score_fxn(nearest_2_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n2_wt_ttm_score <- sum_score_fxn(nearest_2_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)

n3_ttm_score <- sum_score_fxn(nearest_3_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
n3_wt_ttm_score <- sum_score_fxn(nearest_3_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
plot_densities(n1_ttm_score, n1_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n2_ttm_score, n2_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n3_ttm_score, n3_wt_ttm_score, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

using score function of 1/(mean+2sd)

# scores by nearest amenities

n1_ttm_score_2 <- sum_score_fxn_2(nearest_1_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n1_wt_ttm_score_2 <- sum_score_fxn_2(nearest_1_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n2_ttm_score_2 <- sum_score_fxn_2(nearest_2_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n2_wt_ttm_score_2 <- sum_score_fxn_2(nearest_2_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n3_ttm_score_2 <- sum_score_fxn_2(nearest_3_ttm, weight = FALSE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
n3_wt_ttm_score_2 <- sum_score_fxn(nearest_3_ttm, weight = TRUE, log_normalize_score = TRUE, normalize_df = TRUE, x = 1, y = 100)
`summarise()` has grouped output by 'fromId'. You can override using the `.groups` argument.
plot_densities(n1_ttm_score_2, n1_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n2_ttm_score_2, n2_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

plot_densities(n3_ttm_score_2, n3_wt_ttm_score_2, 'Unweighted Scores with 1/(Mean + 2*Sd)', 'Weighted Scores with 1/(Mean+2*Sd)')

Exporting all Score Sets


## Add weight column for each score frame

ttm_scores$weight <- as.factor('no')
ttm_wtd_scores$weight <- as.factor('yes')

n1_ttm_score$weight <- as.factor('no')
n1_wt_ttm_score$weight <- as.factor('yes')

n2_ttm_score$weight <- as.factor('no')
n2_wt_ttm_score$weight <- as.factor('yes')

n3_ttm_score$weight <- as.factor('no')
n3_wt_ttm_score$weight <- as.factor('yes')

## Add nearest_n column for each score frame

ttm_scores$nearest_n <- as.factor('all')
ttm_wtd_scores$nearest_n <- as.factor('all')

n1_ttm_score$nearest_n <- as.factor('1')
n1_wt_ttm_score$nearest_n <- as.factor('1')

n2_ttm_score$nearest_n <- as.factor('2')
n2_wt_ttm_score$nearest_n <- as.factor('2')

n3_ttm_score$nearest_n <- as.factor('3')
n3_wt_ttm_score$nearest_n <- as.factor('3')

## Combine into a long dataframe
all_scores <- list(ttm_scores, ttm_wtd_scores,
                   n1_ttm_score, n1_wt_ttm_score,
                   n2_ttm_score, n2_wt_ttm_score,
                   n3_ttm_score, n3_wt_ttm_score)

long_scores <- data.table::rbindlist(all_scores) %>% arrange(fromId)

## Re-Order columns
long_scores <- long_scores[, c(1, 2, 4, 5, 3)]

## Export
write.csv(long_scores, '../../data/score_sets/long_scores.csv', row.names = FALSE)

Old Notes ~ Ignore or reuse later

Name Function Notes Assumptions
Unweighted Naive number of accessible points / (mean transit time * mean standard deviation in transit time) Mean transit time to all accessible destinations Assumes that accessibility is defined by access to all amenities
Weighted Naive popularity weighted accessible points / (mean transit time * mean standard deviation in transit time) Mean transit time to all accessible destinations Assumes that accessibility is defined by access to all amenities and that amenity popularity defines significance of an accessible amenity
Unweighted Sum 1 / (nearest amenity transit time + standard deviation in nearest transit time) Only considers the nearest 1 to 3 amenities of a certain category. Sum is used to prevent skewing of data (difference(1/(0.01*0.01) and 1/(6*6)) >>> difference(1/(0.01+0.01) and 1/(6+6))) Assumes accessibility only defined by access to the nearest amenity type
Joseph Unweighted Sum sum(1 / (normalized_transit_time_i*normalized_sd_time_i)) Sums the transit times as opposed to taking the mean, then normalizes the scores.
LS0tCnRpdGxlOiAiU2NvcmUgQ29tcHV0YXRpb24gTm90ZWJvb2siCm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCiMgUHVycG9zZQoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBub3RlYm9vayBpcyB0byB1c2UgdGhlIHJhdyB0cmF2ZWwgdGltZSBkYXRhIHRvIGV4cGVyaW1lbnQgd2l0aCBkaWZmZXJlbnQgbWV0aG9kcyBvZiBhZ2dyZWdhdGlvbiBhbmQgc2NvcmUgbW9kZWxpbmcuCgojIyBJbXBvcnQgbGlicmFyaWVzCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCgojIEZvciBwcmV0dHkga25pdHRpbmcKbGlicmFyeShsZW1vbikKa25pdF9wcmludC5kYXRhLmZyYW1lIDwtIGxlbW9uX3ByaW50CmtuaXRfcHJpbnQudGJsIDwtIGxlbW9uX3ByaW50CmtuaXRfcHJpbnQuc3VtbWFyeSA8LSBsZW1vbl9wcmludAoKCmBgYAoKIyMgSW1wb3J0IFNjb3JpbmcgRnVuY3Rpb25zCmBgYHtyfQpzb3VyY2UoJ1Njb3JlX0Z1bmN0aW9ucy5SJykKCiMgbm9ybWFsaXplX3ZlYyh2ZWMsIHg9MC4wMSwgeT0wLjk5LCBsb2cgPSBGQUxTRSkKIyBub3JtYWxpemVfZGYoZGYsIHggPSAwLjAxLCB5ID0gMC45OSwgbG9nID0gRkFMU0UpCgojIHN1bSBzY29yZSBmdW5jdGlvbiA6IFNVTSBbaS4ubl0gKDEgLyAodHJhdmVsdGltZV9pICogc3RkX3RyYXZlbHRpbWVfaSkgKyAuLi4gKSkKIyBzdW1fc2NvcmVfZnhuKGRmLCB3ZWlnaHQgPSBGQUxTRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IEZBTFNFLCB4PTEsIHk9MTApCgpgYGAKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIHBsb3Qgc2NvcmUgZGlzdHJpYnV0aW9ucyBieSB0eXBlCnBsb3RfZGVuc2l0aWVzIDwtIGZ1bmN0aW9uKHNjb3JlX2ZyYW1lMSwgc2NvcmVfZnJhbWUyLCB0aXRsMSwgdGl0bDIpIHsKICB4IDwtIHNjb3JlX2ZyYW1lMSAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBzY29yZSwgY29sb3IgPSB0eXBlKSkgKwogICAgICAgIGdlb21fZGVuc2l0eSgpICsKICAgICAgICBlZ2c6OnRoZW1lX2FydGljbGUoKSArCiAgICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC4zKSArCiAgICAgICAgZ2d0aXRsZSh0aXRsMSkKICB5IDwtIHNjb3JlX2ZyYW1lMiAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKHggPSBzY29yZSwgY29sb3IgPSB0eXBlKSkgKwogICAgICAgIGdlb21fZGVuc2l0eSgpICsKICAgICAgICBlZ2c6OnRoZW1lX2FydGljbGUoKSArCiAgICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC4zKSsKICAgICAgICBnZ3RpdGxlKHRpdGwyKQogIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHgsIHkpCn0KCmBgYAoKCiMjIEltcG9ydCBkYXRhIApgYGB7ciBrYWJsZS5vcHRzPWxpc3QoY2FwdGlvbj0nU3VtbWFyeSBUYWJsZScpfQoKIyMgSW1wb3J0IHJhdyBUcmF2ZWwgVGltZSBNYXRyaXggKHR0bSkKdHRtIDwtIHJlYWQuY3N2KCcuLi8uLi9kYXRhL2NsZWFuL3R0bS5jc3YnKQoKbl9vcmlnaW5zIDwtIDE1MTk3ICMga25vd24gb3JpZ2lucwpuX2FtZW5pdGllcyA8LSAzNDYgIyBrbm93biBkZXN0aW5hdGlvbnMgZnJvbSBjb25zaWRlcmVkIGFtZW5pdGllcwoKcGFzdGUoJ09yaWdpbnMgY29uc2lkZXJlZDonLCByb3VuZChsZW5ndGgodW5pcXVlKHR0bSRmcm9tSWQpKS9uX29yaWdpbnMqMTAwLCAyKSwgJyUnKQpwYXN0ZSgnRGVzdGluYXRpb25zIGNvbnNpZGVyZWQ6Jywgcm91bmQobGVuZ3RoKHVuaXF1ZSh0dG0kdG9JZCkpL25fYW1lbml0aWVzKjEwMCwgMiksICclJykKcGFzdGUoJ1Jvd3MgPSAnLCBucm93KHR0bSkpCgojIGNvbnZlcnQgSWRzIGZyb20gZG91YmxlIHRvIGZhY3Rvcgp0dG0kZnJvbUlkIDwtIGFzLmZhY3Rvcih0dG0kZnJvbUlkKQp0dG0kdG9JZCA8LSBhcy5mYWN0b3IodHRtJHRvSWQpCgpzdW1tYXJ5KHR0bVssMzo0XSkKYGBgCgojIyBEYXRhIFdyYW5nbGluZyAKCioqV3JhbmdsaW5nIE5vdGVzOioqCi0gUmVtb3ZlIHNrZXdzIGFuZCBleHRyZW1lIHZhbHVlcwotIER1ZSB0byB0aGUgZGl2ZXJzaXR5IGluIGFtZW5pdHkgdHlwZXMgKHdoaWNoIGFsbCBzZXJ2ZSBhIHVuaXF1ZSBjdWx0dXJhbCBwdXJwb3NlKSwgd2UnbGwgY29uc2lkZXIgdGhlbSBpbmRlcGVuZGVudGx5IGZvciBhY2Nlc3NpYmlsaXR5IHNjb3JlIGNvbXB1dGF0aW9ucy4KLSBBbWVuaXRpZXMgd2hpY2ggd2VyZSBpbnRlcmVzdGVkIGluIHN0dWR5aW5nIGhhdmUgYWxyZWFkeSBiZWVuIGZpbHRlcmVkIG91dCBpbiB0aGUgdHRtIGNvbXB1dGF0aW9uLiBUaGV5IGFyZSB0aGUgZm9sbG93aW5nOgogIC0gTXVzZXVtcwogIC0gTGlicmFyaWVzCiAgLSBHYWxsZXJpZXMKICAtIFRoZWF0cmVzCgoqKkltcG9ydCBhbmQgam9pbiBhbWVuaXR5IHR5cGVzKioKCmBgYHtyIGthYmxlLm9wdHM9bGlzdChjYXB0aW9uPSdTdW1tYXJ5IFRhYmxlJyl9Cgp0YXJnZXRfYW1lbml0aWVzIDwtIGMoJ2dhbGxlcnknLCAnbXVzZXVtJywgJ2xpYnJhcnkgb3IgYXJjaGl2ZXMnLCAndGhlYXRyZS9wZXJmb3JtYW5jZSBhbmQgY29uY2VydCBoYWxsJykKYW1lbml0aWVzIDwtIHJlYWQuY3N2KCcuLi8uLi9kYXRhL2NsZWFuL3ZhbmNvdXZlcl9mYWNpbGl0aWVzXzIuY3N2JykgJT4lIGZpbHRlcih0eXBlICVpbiUgdGFyZ2V0X2FtZW5pdGllcykKCiMgcHJldmlldyBvcmlnaW5hbApzYW1wbGVfbihhbWVuaXRpZXMsIDMpCgojIGNsZWFuCmFtZW5pdGllcyA8LSBhbWVuaXRpZXNbLGMoMSw0KV0gIyBvbmx5IG5lZWQgaWQgYW5kIHR5cGUgY29sdW1ucwphbWVuaXRpZXMkaWQgPC0gYXMuZmFjdG9yKGFtZW5pdGllcyRpZCkgICAgICMgY29udmVydCB0byBmYWN0b3IKYW1lbml0aWVzJHR5cGUgPC0gYXMuZmFjdG9yKGFtZW5pdGllcyR0eXBlKSAjIGNvbnZlcnQgdG8gZmFjdG9yCgojIHByZXZpZXcgY2xlYW4Kc2FtcGxlX24oYW1lbml0aWVzLCAzKQoKIyB2aWV3IHN1bW1hcnkKYW1lbml0aWVzICU+JSBncm91cF9ieSh0eXBlKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKGNvdW50KSkKCnR0bSA8LSB0dG0gJT4lIGxlZnRfam9pbihhbWVuaXRpZXMsIGJ5ID0gYygndG9JZCcgPSAnaWQnKSkKCm5hbWVzKHR0bSlbbmFtZXModHRtKSA9PSAnYXZnX3VuaXF1ZV90aW1lJ10gPC0gImF2Z190aW1lIgpuYW1lcyh0dG0pW25hbWVzKHR0bSkgPT0gJ3NkX3VuaXF1ZV90aW1lJ10gPC0gInNkX3RpbWUiCgpzdW1tYXJ5KHR0bVssMzo0XSkKc2FtcGxlX24odHRtLCA1KQoKcGFyKG1mcm93ID0gYygxLDIpKQpwbG90KGRlbnNpdHkodHRtWywzXSksIG1haW4gPSAnVHJhdmVsIFRpbWUgKERlbnNpdHkpJykKcGxvdChkZW5zaXR5KHR0bVssNF0pLCBtYWluID0gJ1N0ZCBEZXYgb2YgVHJhdmVsIFRpbWUgKERlbnNpdHkpJykKYGBgCgoKKipSZXBsYWNlIHRyYXZlbCB0aW1lcyBsZXNzIHRoYW4gNSBtaW51dGVzIHRvIDUgbWludXRlcyoqCgpUaGlzIGlzIGRvbmUgdG8gcHJldmVudCBpbmZpbml0eSB2YWx1ZXMgaW4gdGhlIHNjb3JpbmcuIE5vcm1hbGl6YXRpb24gd2lsbCBiZSBkb25lIHRvIHByZXZlbnQgemVybyB2YWx1ZXMgYnV0IGl0IHN0aWxsIGNyZWF0ZXMgYSBsYXJnZWx5IHNrZXdlZCBzY29yZSBpZiB3ZSBpbmNsdWRlIHRyYXZlbCB0aW1lcyB0aGF0IGFwcHJvYWNoIHplcm8uIDUgbWludXRlcyBpcyBhbHNvIGEgcmVhbGlzdGljIHRpbWUgd2luZG93IGZvciBhbnkgdHJhdmVsIHRpbWUgdGhhdCBtYXkgdGFrZSAwIC0gNSBtaW51dGVzLgoKYGBge3J9CnBhcihtZnJvdyA9IGMoMSwgMikpCgpoaXN0KCh0dG0kYXZnX3RpbWUpLCB4bGFiID0gJ09yaWdpbmFsIFRyYXZlbCBUaW1lJywgbWFpbiA9ICcnLAogICAgIHhsaW0gPSBjKDAsIDI1KSwgeWxpbSA9IGMoMCwgMTIwMDAwKSkKCiMgc2V0IHRyYXZlbCB0aW1lcyA8NSBtaW51dGVzIHRvIDUgbWludXRlcwptaW5fNW1pbiA8LSBwbWF4KHR0bSRhdmdfdGltZSwgNSkKaGlzdChtaW5fNW1pbiwgeGxhYiA9ICdPcmlnaW5hbCBUcmF2ZWwgVGltZScsIG1haW4gPSAnJywKICAgICB4bGltID0gYygwLCAyNSksIHlsaW0gPSBjKDAsIDEyMDAwMCkpCgp0dG0kYXZnX3RpbWUgPC0gbWluXzVtaW4KYGBgCgoqKkNvcnJlY3Qgc2tldyBpbiBzdGFuZGFyZCBkZXZpYXRpb24qKgoKVGhpcyB3aWxsIGJlIGltcG9ydGFudCB0byBwcmV2ZW50IHNrZXcgYW1wbGlmaWNhdGlvbiBpbiB0aGUgc2NvcmUgY29tcHV0YXRpb24uCgpgYGB7cn0KIyBjb3JyZWN0IHRoZSBza2V3IGluIGFkZGl0aW9uIHRvIGVkZ2VzIGNsb3NlIHRvIHplcm8KCnRlbXAgPC0gbG9nKHR0bSRzZF90aW1lICsgMSkgIyArMSBqdXN0IHByZXZlbnRzIHplcm8gdmFsdWVzCnBsb3QoZGVuc2l0eSh0ZW1wKSwgbWFpbiA9ICdMb2crMSBTdGFuZGFyZCBEZXZpYXRpb24gRGVuc2l0eScsIHhsaW0gPSBjKDAsNCkpCgojIHNldCBzZF91bmlxdWVfdGltZSB0byBiZSB0aGUgTG9nKzEgY29ycmVjdGVkIHZhbHVlcwp0dG0kc2RfdGltZSA8LSB0ZW1wCgpgYGAKCiMjIEFkZCBBbWVuaXR5IFdlaWdodHMKCmBgYHtyIGthYmxlLm9wdHM9bGlzdChjYXB0aW9uPSdTdW1tYXJ5IFRhYmxlJyl9CgojIEltcG9ydCB3ZWlnaHQKZGVzdF93dHMgPC0gcmVhZC5jc3YoJy4uLy4uL2RhdGEvYW1lbml0eV9zY29yZS9wb2lfaW5kZXguY3N2JykKCiMgY2xlYW4KZGVzdF93dHMgPC0gZGVzdF93dHNbLCBjKDYsNyldICMga2VlcCB3ZWlnaHQsIGlkCm5hbWVzKGRlc3Rfd3RzKSA8LSBjKCd3ZWlnaHQnLCAnaWQnKQpkZXN0X3d0cyRpZCA8LSAgYXMuZmFjdG9yKGRlc3Rfd3RzJGlkKQpoZWFkKGRlc3Rfd3RzKQoKIyBzZWUgd2VpZ2h0IGRpc3RyaWJ1dGlvbgpwbG90KGRlbnNpdHkoZGVzdF93dHMkd2VpZ2h0KSwgbWFpbiA9ICdBbWVuaXR5IFBvcHVsYXJpdHkgRGlzdHJpYnV0aW9uJykKCiMgam9pbiBjb2x1bW4KdHRtX3d0cyA8LSBsZWZ0X2pvaW4odHRtLCBkZXN0X3d0cywgYnkgPSBjKCd0b0lkJz0naWQnKSkKCiMgSWYgYW55IHdlaWdodHMgYXJlIHVuZGVmaW5lZCByZXBsYWNlIHdpdGggMQp0dG1fd3RzJHdlaWdodFtpcy5uYSh0dG1fd3RzJHdlaWdodCldIDwtIDEKCmhlYWQodHRtX3d0cykKCgpgYGAKCiMjIFN1bSBTY29yaW5nIE1ldGhvZAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCiMgc2NvcmVzIHdpdGggWzEgLSAxMDBdIGRmIG5vcm1hbGl6YXRpb24gCgoKbmEub21pdCh0dG1fd3RzKS0+dHRtX3d0cwoKCnR0bV9zY29yZXMgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQoKdHRtX3d0ZF9zY29yZXMgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCmBgYAojIyBTdW0gU2NvcmluZyBNZXRob2QgMiB3aXRoIG1lYW4gcGx1cyBzZAoKYGBge1J9CnR0bV9zY29yZXNfMiA8LSBzdW1fc2NvcmVfZnhuXzIodHRtX3d0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCnR0bV93dGRfc2NvcmVzXzIgPC0gc3VtX3Njb3JlX2Z4bl8yKHR0bV93dHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCmBgYAoKYGBge3J9CnBhcihtZnJvdz1jKDEsNCkpCnBsb3RfZGVuc2l0aWVzKHR0bV9zY29yZXMsIHR0bV93dGRfc2NvcmVzLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKcGxvdF9kZW5zaXRpZXModHRtX3Njb3Jlc18yLCB0dG1fd3RkX3Njb3Jlc18yLCAnVW53ZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuICsgMipTZCknLCAnV2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbisyKlNkKScpCgpgYGAKCgpgYGB7cn0KCiMgc2NvcmVzIHdpdGggWzAuMDEgLSAwLjk5XSBkZiBub3JtYWxpemF0aW9uIAoKdHRtX3Njb3JlczIgPC0gc3VtX3Njb3JlX2Z4bih0dG1fd3RzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAwLjAxLCB5ID0gMC45OSkKCnR0bV93dGRfc2NvcmVzMiA8LSBzdW1fc2NvcmVfZnhuKHR0bV93dHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMC4wMSwgeSA9IDAuOTkpCgpwbG90X2RlbnNpdGllcyh0dG1fc2NvcmVzMiwgdHRtX3d0ZF9zY29yZXMyLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKCmBgYAoKCgojIyBTdW0gU2NvcmluZyBmb3IgdGhlIE5lYXJlc3QgMSwgMiwgb3IgMyBBbWVuaXRpZXMKCipOb3RlIHRoYXQgZm9yIG5lYXJlc3QgMSwgdGhlIHN1bSBpcyB0aGUgdmFsdWUgaXRzZWxmLioKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIEtlZXAgb25seSB0aGUgbmVhcmVzdCAxLCAyLCBvciAzIHRyYXZlbCB0aW1lcyBmb3IgZWFjaCBkaXNzZW1pbmF0aW9uIGJsb2NrCgpuZWFyZXN0XzFfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG1pbihhdmdfdGltZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gubWluKGF2Z190aW1lKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQgPSB3ZWlnaHRbd2hpY2gubWluKGF2Z190aW1lKV0pCgpuZWFyZXN0XzJfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG5hLm9taXQoc29ydChhdmdfdGltZSlbMToyXSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gobmEub21pdChhdmdfdGltZSA9PSBzb3J0KGF2Z190aW1lKVsxOjJdKSldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodFt3aGljaChuYS5vbWl0KGF2Z190aW1lID09IHNvcnQoYXZnX3RpbWUpWzE6Ml0pKV0pCgpuZWFyZXN0XzNfdHRtIDwtIHR0bV93dHMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21JZCwgdHlwZSkgJT4lCiAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShhdmdfdGltZSA9IG5hLm9taXQoc29ydChhdmdfdGltZSlbMTozXSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RfdGltZSA9IHNkX3RpbWVbd2hpY2gobmEub21pdChhdmdfdGltZSA9PSBzb3J0KGF2Z190aW1lKVsxOjNdKSldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodCA9IHdlaWdodFt3aGljaChuYS5vbWl0KGF2Z190aW1lID09IHNvcnQoYXZnX3RpbWUpWzE6M10pKV0pCgoKIyBzY29yZXMgYnkgbmVhcmVzdCBhbWVuaXRpZXMKCm4xX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMV90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4xX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMV90dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCm4yX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4yX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCm4zX3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfM190dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4zX3d0X3R0bV9zY29yZSA8LSBzdW1fc2NvcmVfZnhuKG5lYXJlc3RfM190dG0sIHdlaWdodCA9IFRSVUUsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKCmBgYAoKYGBge3J9CnBsb3RfZGVuc2l0aWVzKG4xX3R0bV9zY29yZSwgbjFfd3RfdHRtX3Njb3JlLCAnVW53ZWlnaHRlZCBTY29yZXMnLCAnV2VpZ2h0ZWQgU2NvcmVzJykKcGxvdF9kZW5zaXRpZXMobjJfdHRtX3Njb3JlLCBuMl93dF90dG1fc2NvcmUsICdVbndlaWdodGVkIFNjb3JlcycsICdXZWlnaHRlZCBTY29yZXMnKQpwbG90X2RlbnNpdGllcyhuM190dG1fc2NvcmUsIG4zX3d0X3R0bV9zY29yZSwgJ1Vud2VpZ2h0ZWQgU2NvcmVzJywgJ1dlaWdodGVkIFNjb3JlcycpCgpgYGAKCiMjIyB1c2luZyBzY29yZSBmdW5jdGlvbiBvZiAxLyhtZWFuKzJzZCkKCgpgYGB7cn0KIyBzY29yZXMgYnkgbmVhcmVzdCBhbWVuaXRpZXMKCm4xX3R0bV9zY29yZV8yIDwtIHN1bV9zY29yZV9meG5fMihuZWFyZXN0XzFfdHRtLCB3ZWlnaHQgPSBGQUxTRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQpuMV93dF90dG1fc2NvcmVfMiA8LSBzdW1fc2NvcmVfZnhuXzIobmVhcmVzdF8xX3R0bSwgd2VpZ2h0ID0gVFJVRSwgbG9nX25vcm1hbGl6ZV9zY29yZSA9IFRSVUUsIG5vcm1hbGl6ZV9kZiA9IFRSVUUsIHggPSAxLCB5ID0gMTAwKQoKbjJfdHRtX3Njb3JlXzIgPC0gc3VtX3Njb3JlX2Z4bl8yKG5lYXJlc3RfMl90dG0sIHdlaWdodCA9IEZBTFNFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCm4yX3d0X3R0bV9zY29yZV8yIDwtIHN1bV9zY29yZV9meG5fMihuZWFyZXN0XzJfdHRtLCB3ZWlnaHQgPSBUUlVFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCgpuM190dG1fc2NvcmVfMiA8LSBzdW1fc2NvcmVfZnhuXzIobmVhcmVzdF8zX3R0bSwgd2VpZ2h0ID0gRkFMU0UsIGxvZ19ub3JtYWxpemVfc2NvcmUgPSBUUlVFLCBub3JtYWxpemVfZGYgPSBUUlVFLCB4ID0gMSwgeSA9IDEwMCkKbjNfd3RfdHRtX3Njb3JlXzIgPC0gc3VtX3Njb3JlX2Z4bihuZWFyZXN0XzNfdHRtLCB3ZWlnaHQgPSBUUlVFLCBsb2dfbm9ybWFsaXplX3Njb3JlID0gVFJVRSwgbm9ybWFsaXplX2RmID0gVFJVRSwgeCA9IDEsIHkgPSAxMDApCgpgYGAKCgpgYGB7cn0KcGxvdF9kZW5zaXRpZXMobjFfdHRtX3Njb3JlXzIsIG4xX3d0X3R0bV9zY29yZV8yLCAnVW53ZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuICsgMipTZCknLCAnV2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbisyKlNkKScpCnBsb3RfZGVuc2l0aWVzKG4yX3R0bV9zY29yZV8yLCBuMl93dF90dG1fc2NvcmVfMiwgJ1Vud2VpZ2h0ZWQgU2NvcmVzIHdpdGggMS8oTWVhbiArIDIqU2QpJywgJ1dlaWdodGVkIFNjb3JlcyB3aXRoIDEvKE1lYW4rMipTZCknKQpwbG90X2RlbnNpdGllcyhuM190dG1fc2NvcmVfMiwgbjNfd3RfdHRtX3Njb3JlXzIsICdVbndlaWdodGVkIFNjb3JlcyB3aXRoIDEvKE1lYW4gKyAyKlNkKScsICdXZWlnaHRlZCBTY29yZXMgd2l0aCAxLyhNZWFuKzIqU2QpJykKCmBgYAoKIyMgRXhwb3J0aW5nIGFsbCBTY29yZSBTZXRzCgpgYGB7cn0KCiMjIEFkZCB3ZWlnaHQgY29sdW1uIGZvciBlYWNoIHNjb3JlIGZyYW1lCgp0dG1fc2NvcmVzJHdlaWdodCA8LSBhcy5mYWN0b3IoJ25vJykKdHRtX3d0ZF9zY29yZXMkd2VpZ2h0IDwtIGFzLmZhY3RvcigneWVzJykKCm4xX3R0bV9zY29yZSR3ZWlnaHQgPC0gYXMuZmFjdG9yKCdubycpCm4xX3d0X3R0bV9zY29yZSR3ZWlnaHQgPC0gYXMuZmFjdG9yKCd5ZXMnKQoKbjJfdHRtX3Njb3JlJHdlaWdodCA8LSBhcy5mYWN0b3IoJ25vJykKbjJfd3RfdHRtX3Njb3JlJHdlaWdodCA8LSBhcy5mYWN0b3IoJ3llcycpCgpuM190dG1fc2NvcmUkd2VpZ2h0IDwtIGFzLmZhY3Rvcignbm8nKQpuM193dF90dG1fc2NvcmUkd2VpZ2h0IDwtIGFzLmZhY3RvcigneWVzJykKCiMjIEFkZCBuZWFyZXN0X24gY29sdW1uIGZvciBlYWNoIHNjb3JlIGZyYW1lCgp0dG1fc2NvcmVzJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJ2FsbCcpCnR0bV93dGRfc2NvcmVzJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJ2FsbCcpCgpuMV90dG1fc2NvcmUkbmVhcmVzdF9uIDwtIGFzLmZhY3RvcignMScpCm4xX3d0X3R0bV9zY29yZSRuZWFyZXN0X24gPC0gYXMuZmFjdG9yKCcxJykKCm4yX3R0bV9zY29yZSRuZWFyZXN0X24gPC0gYXMuZmFjdG9yKCcyJykKbjJfd3RfdHRtX3Njb3JlJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJzInKQoKbjNfdHRtX3Njb3JlJG5lYXJlc3RfbiA8LSBhcy5mYWN0b3IoJzMnKQpuM193dF90dG1fc2NvcmUkbmVhcmVzdF9uIDwtIGFzLmZhY3RvcignMycpCgojIyBDb21iaW5lIGludG8gYSBsb25nIGRhdGFmcmFtZQphbGxfc2NvcmVzIDwtIGxpc3QodHRtX3Njb3JlcywgdHRtX3d0ZF9zY29yZXMsCiAgICAgICAgICAgICAgICAgICBuMV90dG1fc2NvcmUsIG4xX3d0X3R0bV9zY29yZSwKICAgICAgICAgICAgICAgICAgIG4yX3R0bV9zY29yZSwgbjJfd3RfdHRtX3Njb3JlLAogICAgICAgICAgICAgICAgICAgbjNfdHRtX3Njb3JlLCBuM193dF90dG1fc2NvcmUpCgpsb25nX3Njb3JlcyA8LSBkYXRhLnRhYmxlOjpyYmluZGxpc3QoYWxsX3Njb3JlcykgJT4lIGFycmFuZ2UoZnJvbUlkKQoKIyMgUmUtT3JkZXIgY29sdW1ucwpsb25nX3Njb3JlcyA8LSBsb25nX3Njb3Jlc1ssIGMoMSwgMiwgNCwgNSwgMyldCgojIyBFeHBvcnQKd3JpdGUuY3N2KGxvbmdfc2NvcmVzLCAnLi4vLi4vZGF0YS9zY29yZV9zZXRzL2xvbmdfc2NvcmVzLmNzdicsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgoKCgoKCiMgT2xkIE5vdGVzIH4gSWdub3JlIG9yIHJldXNlIGxhdGVyCgp8IE5hbWUgfCBGdW5jdGlvbiB8IE5vdGVzIHwgQXNzdW1wdGlvbnMgfAp8LS0tfC0tLXwtLS18LS0tfAp8VW53ZWlnaHRlZCBOYWl2ZSB8IG51bWJlciBvZiBhY2Nlc3NpYmxlIHBvaW50cyAvIChtZWFuIHRyYW5zaXQgdGltZSAqIG1lYW4gc3RhbmRhcmQgZGV2aWF0aW9uIGluIHRyYW5zaXQgdGltZSkgIHwgTWVhbiB0cmFuc2l0IHRpbWUgdG8gYWxsIGFjY2Vzc2libGUgZGVzdGluYXRpb25zICB8IEFzc3VtZXMgdGhhdCBhY2Nlc3NpYmlsaXR5IGlzIGRlZmluZWQgYnkgYWNjZXNzIHRvIGFsbCBhbWVuaXRpZXMgfAp8V2VpZ2h0ZWQgTmFpdmUgfCBwb3B1bGFyaXR5IHdlaWdodGVkIGFjY2Vzc2libGUgcG9pbnRzIC8gKG1lYW4gdHJhbnNpdCB0aW1lICogbWVhbiBzdGFuZGFyZCBkZXZpYXRpb24gaW4gdHJhbnNpdCB0aW1lKSAgfCBNZWFuIHRyYW5zaXQgdGltZSB0byBhbGwgYWNjZXNzaWJsZSBkZXN0aW5hdGlvbnMgIHwgQXNzdW1lcyB0aGF0IGFjY2Vzc2liaWxpdHkgaXMgZGVmaW5lZCBieSBhY2Nlc3MgdG8gYWxsIGFtZW5pdGllcyBhbmQgdGhhdCBhbWVuaXR5IHBvcHVsYXJpdHkgZGVmaW5lcyBzaWduaWZpY2FuY2Ugb2YgYW4gYWNjZXNzaWJsZSBhbWVuaXR5IHwKfFVud2VpZ2h0ZWQgU3VtIHwgMSAvIChuZWFyZXN0IGFtZW5pdHkgdHJhbnNpdCB0aW1lICsgc3RhbmRhcmQgZGV2aWF0aW9uIGluIG5lYXJlc3QgdHJhbnNpdCB0aW1lKSAgfCBPbmx5IGNvbnNpZGVycyB0aGUgbmVhcmVzdCAxIHRvIDMgYW1lbml0aWVzIG9mIGEgY2VydGFpbiBjYXRlZ29yeS4gU3VtIGlzIHVzZWQgdG8gcHJldmVudCBza2V3aW5nIG9mIGRhdGEgKGRpZmZlcmVuY2UoMS8oMC4wMVwqMC4wMSkgYW5kIDEvKDZcKjYpKSA+Pj4gZGlmZmVyZW5jZSgxLygwLjAxKzAuMDEpIGFuZCAxLyg2KzYpKSkgfCBBc3N1bWVzIGFjY2Vzc2liaWxpdHkgb25seSBkZWZpbmVkIGJ5IGFjY2VzcyB0byB0aGUgbmVhcmVzdCBhbWVuaXR5IHR5cGUgIHwKfCBKb3NlcGggVW53ZWlnaHRlZCBTdW0gfCBzdW0oMSAvIChub3JtYWxpemVkX3RyYW5zaXRfdGltZV9pKm5vcm1hbGl6ZWRfc2RfdGltZV9pKSkgfCBTdW1zIHRoZSB0cmFuc2l0IHRpbWVzIGFzIG9wcG9zZWQgdG8gdGFraW5nIHRoZSBtZWFuLCB0aGVuIG5vcm1hbGl6ZXMgdGhlIHNjb3Jlcy4gfAoKCgoKCgoKCgoKCgoKCgoK